﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;

namespace Argos.Windows.Forms
{
	/// <summary>
	/// A control that encapsulates the <see cref="PropertyGrid"/> control
	/// and provides additional functionality.
	/// </summary>
	public partial class PropertyEditor: UserControl
	{

		#region #ctor: public PropertyEditor()

		/// <summary>
		/// Creates a new instance of the class.
		/// </summary>
		public PropertyEditor()
		{
			InitializeComponent();
			this.PropertySort = PropertySort.CategorizedAlphabetical;
			this.ToolbarVisible = true;
		}

		#endregion

		#region #ctor: public PropertyEditor(object selectedObject)

		/// <summary>
		/// Creates a new instance of the class and sets the object to be edited in the control.
		/// </summary>
		/// <param name="selectedObject">The object to edit.</param>
		public PropertyEditor(object selectedObject) : this()
		{
			this.SelectedObject = selectedObject;
		}

		#endregion

		private EventHandler SelectedObjectChangedHandler;

		/// <summary>
		/// Fired when the <see cref="SelectedObject"/> property
		/// </summary>
		public event EventHandler SelectedObjectChanged
		{
			add { this.SelectedObjectChangedHandler += value; }
			remove { this.SelectedObjectChangedHandler -= value; }
		}



		#region public event EventHandler<PropertyValueChangedEventArgs> PropertyValueChanged

		private EventHandler<PropertyValueChangedEventArgs> PropertyValueChangedHandler;

		/// <summary>
		/// Fired when the value of a property has changed.
		/// </summary>
		public event EventHandler<PropertyValueChangedEventArgs> PropertyValueChanged
		{
			add { this.PropertyValueChangedHandler += value; }
			remove { this.PropertyValueChangedHandler -= value; }
		}

		#endregion


		[DefaultValue(PropertySort.CategorizedAlphabetical)]
		public PropertySort PropertySort
		{
			get { return this.Grid.PropertySort; }
			set { this.Grid.PropertySort = value; }
		}

		/// <summary>
		/// Sets or returns the object to edit.
		/// </summary>
		[Browsable(false)]
		[DefaultValue(null)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public object SelectedObject
		{
			get { return this.Grid.SelectedObject; }
			set { this.Grid.SelectedObject = value; }
		}

		[DefaultValue(true)]
		public bool ToolbarVisible
		{
			get { return this.Grid.ToolbarVisible; }
			set { this.Grid.ToolbarVisible = value; }
		}

		/// <summary>
		/// Returns a dictionary containing the properties of the object being edited.
		/// </summary>
		public IDictionary<string, object> GetProperties()
		{
			return this.GetProperties(p => true);
		}

		/// <summary>
		/// Returns a dictionary containing the selected properties of the object
		/// being edited.
		/// </summary>
		/// <param name="propertyMatcher">A predicate that matches the properties to include in the dictionary.</param>
		public IDictionary<string, object> GetProperties(Predicate<PropertyInfo> propertyMatcher)
		{
			if (null == propertyMatcher)
			{
				throw new ArgumentNullException("propertyMatcher");
			}

			Dictionary<string, object> properties = new Dictionary<string, object>();

			if (null != this.SelectedObject)
			{
				foreach (PropertyInfo pi in this.SelectedObject.GetType().GetProperties())
				{
					if (propertyMatcher.Invoke(pi))
					{
						properties.Add(
							pi.Name,
							this.SelectedObject.GetType().InvokeMember(
								pi.Name,
								BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
								null,
								this.SelectedObject,
								new object[0]
							)
						);
					}
				}
			}

			return properties;
		}



		/// <summary>
		/// Fires the <see cref="PropertyValueChanged"/> event.
		/// </summary>
		protected virtual void OnPropertyValueChanged(PropertyValueChangedEventArgs e)
		{
			if (null != this.PropertyValueChangedHandler)
			{
				this.PropertyValueChangedHandler.Invoke(this, e);
			}
		}

		/// <summary>
		/// Fires the <see cref="SelectedObjectChanged"/> event.
		/// </summary>
		protected virtual void OnSelectedObjectChanged(EventArgs e)
		{
			if (null != this.SelectedObjectChangedHandler)
			{
				this.SelectedObjectChangedHandler.Invoke(this, e);
			}
		}



		private void Grid_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
		{
			this.OnPropertyValueChanged(e);
		}

		private void Grid_SelectedObjectsChanged(object sender, EventArgs e)
		{
			this.OnSelectedObjectChanged(e);
		}
		
	}
}
